﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sanford.Multimedia.Midi;
using Sanford.Multimedia;
using System.IO;
using Brio.Framework;

namespace Brio
{
    public delegate void GenerationChangedEventHandler(object sender, int generation);

    public class Brio
    {
        public Brio()
        {
            // Step 1: Load in all Techniques into the Collection
            Techniques = new TechniqueCollection();

            Techniques.AddDemo();

            // Step 2: Load in all Musical Operators into the Collection
            MusicalOperators = new MusicalOperatorCollection();

            MusicalOperators.AddOperators();

            IntervalRelationships = new IntervalRelationshipTable();

            IntervalRelationships.InitializeGenericPopTable();
        }

        #region Public Properties

        public MotifPopulation Population
        { get; set; }

        public TechniqueCollection Techniques
        { get; set; }

        public MusicalOperatorCollection MusicalOperators
        { get; set; }

        public IntervalRelationshipTable IntervalRelationships
        { get; set; }


        #endregion


        public event GenerationChangedEventHandler GenerationChanged;

        public List<MotifChromosome> Run()
        {

         
            Population = new MotifPopulation();

            // ====================================
            // RUN
            // ====================================
            for (int R = 0; R < Settings.Runs; R++)
            {
                Population.Clear();

                // Step 1 : Create a new population
                for (int p = 0; p < Settings.PopulationSize; p++)
                {
                    MotifChromosome motif = new MotifChromosome();

                    Population.Add(motif);
                }

                // =================================
                // GENERATION
                // =================================
                for (int G = 0; G < Settings.Generations; G++)
                {
                    // Fire event handler that you can subscribe to which
                    // shows how far along the GA is.
                    if (GenerationChanged != null)
                    {
                        GenerationChanged(this, G);
                    }

                    // =================================
                    // POPULATION
                    // =================================
                    // Step 2 : Perform fitness function for each member in the population
                    // Note: DoFitness() zeros out the last fitness values using an internal
                    // DoPreFitness function.
                    for (int P = 0; P < Settings.PopulationSize; P++)
                        Population[P].DoFitness(Techniques, G == Settings.Generations - 1);

                    // Step X: Compute Average Fitness and STD statistics
                    Population.ComputeGenerationalStatistics();

                    // Step 3: Sort the whole population using a custom sorting operator
                    // Since we are talking about musical motifs it's hard to identify a concept of
                    // What is the best melody created therefore we will most likely use this sorted 
                    // population to perform some kind of percentile statistics
                    Population.Sort();

                    // Step 4: Scale all fitnesses according to scaling function 
                    // And normalize?
                    Population.ScaleFitnesses();

                    // Step 5 : Select population based on selection method
                    Population.Select();



                    if (G != Settings.Generations - 1)
                    {
                        // Step 5: Apply both haploid and diploid operators to new population
                        for (int P = 0; P < Settings.PopulationSize; P++)
                        {
                            // Basic Functionality: Goes through the colleciton and sets what operator should be applied based on the percentages
                            // Also the operator can look at the current motif chromosome and decide what operators the motif
                            // might need and set them based on that
                            MusicalOperators.Begin(Population);

                            foreach (MusicalOperator op in MusicalOperators)
                            {
                                if (op.ShouldApply)
                                {
                                    // One Parent
                                    if (op.Type == OperatorType.Haploid)
                                    {
                                        // Some custom logic to incorporate the idea of a feedback loop.
                                        if (op.Name.Equals("ViralInjection", StringComparison.InvariantCultureIgnoreCase))
                                        {
                                            op.Apply(Population[P], Techniques);
                                        }
                                        else if (op.Name.Equals("Smoothing", StringComparison.InvariantCultureIgnoreCase))
                                        {
                                            op.Apply(Population[P], IntervalRelationships);
                                        }
                                        else
                                        {
                                            op.Apply(Population[P]);
                                        }
                                    }
                                    // Two Parent
                                    else //if (op.Type == OperatorType.Diploid)
                                    {
                                        // Who is the doner and who is the receiver, analyze and correct if necessary
                                        int randomMotifIndex = Constants.Random.Next(P);
                                        op.Apply(Population[randomMotifIndex], Population[P]);
                                    }

                                }
                            }

                            // Any post application code can be applied after
                            MusicalOperators.End();
                        }
                    }

                }

                Population.ComputeSingleRunStatistics();

            }

            Population.ComputeEntireRunStatistics();

            return Population.GetTopPercentage(Settings.BestPercentage);

        }


        public void SaveChromosome(OutputDevice device, MotifChromosome chromosome, string directoryName)
        {
            // Create Saved Chromosome Directories
            Directory.CreateDirectory(directoryName);
            Directory.CreateDirectory(directoryName + "/Sheet Music/");

            string prettyName = directoryName.Substring(directoryName.LastIndexOf("/") + 1);

            // Save the MIDI
            SaveMIDI(device, chromosome, directoryName + "/" + prettyName + ".mid");
            // Save the Sheet Music
            bool success = SaveSheetMusic(directoryName + "/" + prettyName + ".mid", directoryName + "/Sheet Music/" + prettyName + ".png");
            // Save Metrics
            SaveMetrics(chromosome, directoryName + "/" + prettyName + ".txt");

            // Prompt user that they need MuseScore!
            if (!success)
            {
                System.Windows.Forms.MessageBox.Show("MuseScore not installed. No Sheet Music could be outputted.");
            }
        }

        private void SaveMetrics(MotifChromosome chromosome, string fileName)
        {
            // Correct file name
            fileName = fileName.Replace("/", "\\");

            FileInfo f = new FileInfo(fileName);
            StreamWriter writer = f.CreateText();

            string report = chromosome.Metrics.ToMetricReportString();

            writer.Write(report);

            // Remember to close the writer to output the file immediately.
            writer.Close();
        }

        public void SaveMIDI(OutputDevice device, MotifChromosome chromosome, string fileName)
        {
            //////////////////////////////////
            // Step 1) Initialize variables //
            //////////////////////////////////

            double position;

            Random random = new Random();

            const int DIVISION = 24;
            const int EIGTHNOTE = 8;
            const int TPQN = DIVISION / EIGTHNOTE; // Ticks per eigth notea

            /////////////////////////////////////////////////////////
            // Step 2) Create Sequence and Channel Message Builder //
            /////////////////////////////////////////////////////////

            Sequence sequence = new Sequence(DIVISION);
            ChannelMessageBuilder builder = new ChannelMessageBuilder()
            {
                Command = ChannelCommand.ProgramChange
            };

            Track track = new Track();

            sequence.Add(track);

            int CHANNEL = 0;

            builder.MidiChannel = CHANNEL;
            builder.Data1 = 4; // 80;
            builder.Build();
            track.Insert(0, builder.Result);
            device.Send(builder.Result);

            position = 0;

            /////////////////////////////
            // Step 3) Set Tempo       //
            /////////////////////////////
            TempoChangeBuilder tempoBuilder = new TempoChangeBuilder();
            tempoBuilder.Tempo = 60000000 / chromosome.Staff.Tempo;
            tempoBuilder.Build();
            track.Insert(0, tempoBuilder.Result);


            /////////////////////////////
            // Step 4) Set Key         //
            // Note: This is for       //
            // testing purposes! WORKS //
            /////////////////////////////
            /*
            KeySignatureBuilder keySignatureBuilder = new KeySignatureBuilder();
            keySignatureBuilder.Key = Key.CMajor;
            keySignatureBuilder.Build();
            track.Insert(0, keySignatureBuilder.Result);
            */

            ////////////////////////////////
            // Step 5) Build Track        //
            ////////////////////////////////
            // TODO: Handle Rest Notes
            for (int i = 0; i < chromosome.Staff.Rhythm.Length; i++)
            {
                int multiplier = 32 / Framework.Constants.SMALLEST_RHYTHMIC_UNIT;

                int noteLength = chromosome.Staff.Rhythm[i];
                int noteValue = chromosome.Staff[chromosome.Staff.GetStaffIndexFromRhythmIndex(i)].Value;

                track.Insert((int)(position * TPQN), new ChannelMessage(ChannelCommand.NoteOn, CHANNEL, noteValue, 88));

                position += noteLength * multiplier;

                track.Insert((int)(position * TPQN), new ChannelMessage(ChannelCommand.NoteOff, CHANNEL, noteValue, 0));
            }

            ////////////////////////////////
            // Step 6) Save MIDI File     //
            ////////////////////////////////
            Sequencer sequencer = new Sequencer()
            {
                Sequence = sequence
            };

            sequence.Save(fileName);
        }

        public bool SaveSheetMusic(string midiFileName, string sheetMusicFileName)
        {
            string museScoreDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\MuseScore\\bin\\";

            string museScoreEXE = museScoreDirectory + "mscore.exe";
            string museScoreCommand = museScoreDirectory + "mscore";

            // Correct file names
            midiFileName = midiFileName.Replace("/", "\\");
            sheetMusicFileName = sheetMusicFileName.Replace("/", "\\");


            // Great! You have Muse Score installed. We can output PNGs.
            if (File.Exists(museScoreEXE))
            {
                string sheetMusicCommand = "";

                sheetMusicCommand += "\"" + midiFileName + "\" ";
                sheetMusicCommand += "-o \"" + sheetMusicFileName + "\"";

                try
                {
                    // Now we create a process, assign its ProcessStartInfo and start it
                    System.Diagnostics.Process proc = new System.Diagnostics.Process();

                    proc.StartInfo.UseShellExecute = false;
                    proc.StartInfo.RedirectStandardOutput = true;
                    proc.StartInfo.CreateNoWindow = false;
                    proc.StartInfo.FileName = museScoreCommand;
                    proc.StartInfo.Arguments = sheetMusicCommand;

                    proc.Start();

                    proc.WaitForExit();
                    // Get the output into a string
                    string result = proc.StandardOutput.ReadToEnd();
                    // Display the command output.
                    Console.WriteLine(result);
                }
                catch (Exception objException)
                {
                    // Log the exception
                }

                return true;
            }
            else
            {
                return false;
            }
        }

        /*
        public MotifChromosome InjectTechnique()
        {
            MotifChromosome chromo = new MotifChromosome();

            // Redefine staff
            chromo.Staff = new Staff(4, ScaleMask.CMajor);


            Techniques = new TechniqueCollection();

            Techniques.AddScottJoplin();

            int injectionPoint;
            int techniqueIndex;
           

            for (int i = 0; i < 100; i++)
            {
                injectionPoint = Constants.Random.Next(chromo.Staff.Measures * 4);
                techniqueIndex = Constants.Random.Next(Techniques.Count);

                chromo.Staff.Inject(Techniques.ElementAt(techniqueIndex).Value, injectionPoint);
                


                // Test code
                double joinPercentage = 0.50;
                double rJoin = Constants.Random.NextDouble();

                double splitPercentage = 0.50;
                double rSplit = Constants.Random.NextDouble();


                if (rJoin < joinPercentage)
                {
                    int rIndex = Constants.Random.Next(chromo.Staff.Rhythm.Length);

                    chromo.Staff.Join(rIndex);

                }

                if (rSplit < splitPercentage)
                {
                    int rIndex = Constants.Random.Next(chromo.Staff.Rhythm.Length);

                    //if (mutatedStaff.Rhythm[rIndex] > 8)
                    chromo.Staff.Split(rIndex);
                }
                
            }
            
            return chromo;
        }
        */

    }
}
